package pl.edu.mimuw.irs.core.model.simulation.atomic;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.LinkedHashSet;
import java.util.Set;

import pl.edu.mimuw.irs.core.global.IDManager;
import pl.edu.mimuw.irs.core.model.analysis.atomic.ActivityPlace;
import pl.edu.mimuw.irs.core.model.petrinet.Arc;
import pl.edu.mimuw.irs.core.model.petrinet.Place;
import pl.edu.mimuw.irs.core.model.petrinet.Transition;
import pl.edu.mimuw.irs.core.util.Position;

public class EventTransition extends Transition implements PropertyChangeListener {

	private static final long serialVersionUID = -1191361970996518491L;
	
	// *** Properties
	
	public static final String ACTIVE = "Active";
	
	// *** Attributes
	
	private boolean active;
	private boolean working;
	private boolean starting;
	private Set<Arc> startingPlaces;
	private Set<Arc> endingPlaces;
	private Set<Arc> startingActivities;
	private Set<Arc> endingActivities;
	@Override
	public boolean isActive() {
		return active;
	}
	protected void setActive(boolean active) {
		this.active = active;
	}
	public Set<Arc> getStartingPlaces() {
		if (startingPlaces == null) init();
		return startingPlaces;
	}
	public Set<Arc> getEndingPlaces() {
		if (endingPlaces == null) init();
		return endingPlaces;
	}
	public Set<Arc> getStartingActivities() {
		if (startingActivities == null) init();
		return startingActivities;
	}
	public Set<Arc> getEndingActivities() {
		if (endingActivities == null) init();
		return endingActivities;
	}
	@Override
	public void addArc(Arc arc) {
		super.addArc(arc);
		arc.getPlace().addPropertyChangeListener(EventPlace.TOKENS, this);
		arc.getPlace().addPropertyChangeListener(EventPlace.BUSY, this);
	}
	@Override
	public void removeArc(Arc arc) {
		super.removeArc(arc);
		arc.getPlace().removePropertyChangeListener(this);
	}

	// *** Constructors
	
	public EventTransition(IDManager idManager, String id, 
			Position position) {
		super(idManager, id, position);
	}
	
	// *** Methods
	
	private void init() {
		startingPlaces = new LinkedHashSet<Arc>();
		endingPlaces = new LinkedHashSet<Arc>();
		startingActivities = new LinkedHashSet<Arc>();
		endingActivities = new LinkedHashSet<Arc>();
		
		for (Arc incomingArc : getIncomingArcs()) {
			if (incomingArc.getPlace() instanceof ActivityPlace)
				getStartingActivities().add(incomingArc);
			else
				getStartingPlaces().add(incomingArc);
		}
		for (Arc outgoingArc : getOutgoingArcs()) {
			if (outgoingArc.getPlace() instanceof ActivityPlace)
				getEndingActivities().add(outgoingArc);
			else
				getEndingPlaces().add(outgoingArc);
		}
	}
	
	public boolean isCurrentlyActive() {
		if (!working) return super.isActive();
		else {
			Arc arc = getNextEvent();
			if (arc == null) {
				working = false;
				return super.isActive();
			}
			return getNextEvent().isActive();
		}
	}
	
	public Arc getNextEvent() {
		/*
		 * Returns arc for the next activity to be started/stopped.
		 */
		Arc nextArc = null;
		
		if (starting) {
			for (Arc starting : getStartingActivities()) {
				if (!(starting.getPlace() instanceof EventPlace)) continue;
				EventPlace eventPlace = (EventPlace) starting.getPlace();
				if (eventPlace.isBusy()) continue;
				/* Set this activity arc if it is the eldest */
				if (nextArc == null) nextArc = starting;
				else {
					EventPlace nextPlace = (EventPlace) nextArc.getPlace();
					EventPlace place = (EventPlace) starting.getPlace();
					if (place.getRequirement().getActivity().isAncestorOf(
							nextPlace.getRequirement().getActivity())) 
						nextArc = starting;
				}
			}
			if (nextArc == null) starting = false;
			else nextArc = new BusyEvent(nextArc);
		}
		if (!starting) {
			for (Arc ending : getEndingActivities()) {
				if (!(ending.getPlace() instanceof EventPlace)) continue;
				EventPlace eventPlace = (EventPlace) ending.getPlace();
				if (!eventPlace.isBusy()) continue;
				/* Set this activity arc if it is the youngest */
				if (nextArc == null) nextArc = ending;
				else {
					EventPlace nextPlace = (EventPlace) nextArc.getPlace();
					EventPlace place = (EventPlace) ending.getPlace();
					if (nextPlace.getRequirement().getActivity().isAncestorOf(
							place.getRequirement().getActivity())) 
						nextArc = ending;
				}
			}
			if (nextArc != null) nextArc = new FreeEvent(nextArc);
		}
		
		/*
		 * Note - this still can be null.
		 */
		return nextArc;
	}

	@Override
	public boolean invoke() {
		/* 
		 * If started - we need to get all the incoming tokens from
		 * normal places. Each activity place will be taken in the next
		 * executions (and in the end they will be returned).
		 */
		if (!working) {
			working = true;
			starting = true;
			for (Arc arc : getStartingPlaces()) {
				arc.invoke();
			}
		}
		
		/*
		 * Knowing that at this point this activity transition is started we can
		 * execute starting/ending nested activities.
		 */
		Arc nextEvent = getNextEvent();
		if (nextEvent != null) {
			nextEvent.invoke();
			nextEvent = getNextEvent();
		}
		if (nextEvent == null) {
			working = false;
			for (Arc arc : getEndingPlaces()) {
				arc.invoke();
			}
		}
		
		return true;
	}
	
	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		if (evt.getPropertyName().equals(Place.TOKENS)) {
			boolean oldActive = isActive();
			boolean newActive = isCurrentlyActive();
			if (oldActive != newActive) {
				setActive(newActive);
				getListeners().firePropertyChange(ACTIVE, oldActive, newActive);
			}
		}
	}
	
	public class BusyEvent extends Arc {

		private static final long serialVersionUID = -2899030256672416361L;
		
		private Arc arc;
		
		public BusyEvent(Arc arc) {
			this.arc = arc;
		}
		
		@Override
		public void invoke() {
			arc.invoke();
			EventPlace eventPlace = (EventPlace) arc.getPlace();
			eventPlace.setBusy(true);
		}
	}

	public class FreeEvent extends Arc {

		private static final long serialVersionUID = -6241446681687844673L;

		private Arc arc;
		
		public FreeEvent(Arc arc) {
			this.arc = arc;
		}
		
		@Override
		public void invoke() {
			arc.invoke();
			EventPlace eventPlace = (EventPlace) arc.getPlace();
			eventPlace.setBusy(false);
		}
	}
}
